/**
 * 
 */
package net.svenbuschbeck.gwt.ao.client;

import net.svenbuschbeck.gwt.ao.client.ActivePoint.Point;
import net.svenbuschbeck.gwt.ao.client.events.ValueChangedEvent;
import net.svenbuschbeck.gwt.ao.client.events.ValueChangedHandler;

/**
 * 
 * 
 * @author Sven Buschbeck
 * @param <T>
 * 
 */
public class ActiveRectangle<T> extends ChangedHandler<ActiveRectangle.Rectangle<T>> {
	public static abstract class Rectangle<T> {
		public abstract T getX();

		public abstract T getY();

		public abstract T getWidth();

		public abstract T getHeight();

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#toString()
		 */
		@Override public String toString() {
			return this.getClass().getName() + "(x=" + getX() + ", y=" + getY() + ", width=" + getWidth() + ", height="
				+ getHeight() + ")";
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#equals(java.lang.Object)
		 */
		@Override public boolean equals(Object obj) {
			if (obj != null && obj instanceof ActiveRectangle.Rectangle<?>) {
				ActiveRectangle.Rectangle<?> rectangle = (ActiveRectangle.Rectangle<?>) obj;
				return rectangle.getX() == getX() && rectangle.getY() == getY() && rectangle.getWidth() == getWidth()
					&& rectangle.getHeight() == getHeight();
			}
			return false;
		}
	}

	public final ActivePoint<T> origin = new ActivePoint<T>();
	public final ActivePoint<T> dimensions = new ActivePoint<T>();
	private boolean ignoreValueChanged = false;

	public ActiveRectangle() {
		this(null, null, null, null);
	}

	public ActiveRectangle(T x, T y, T width, T height) {
		set(x, y, width, height);
		origin.addValueChangedHandler(new ValueChangedHandler<Point<T>>() {

			@Override public void onValueChanged(ValueChangedEvent<Point<T>> event) {
				fireEvent(
					createRectangle(event.getOldValue().getX(), event.getOldValue().getY(), dimensions.x.get(),
						dimensions.y.get()),
					createRectangle(event.getNewValue().getX(), event.getNewValue().getY(), dimensions.x.get(),
						dimensions.y.get()));
			}
		});
		dimensions.addValueChangedHandler(new ValueChangedHandler<Point<T>>() {

			@Override public void onValueChanged(ValueChangedEvent<Point<T>> event) {
				fireEvent(
					createRectangle(origin.x.get(), origin.y.get(), event.getOldValue().getX(), event.getOldValue()
						.getY()),
					createRectangle(origin.x.get(), origin.y.get(), event.getNewValue().getX(), event.getNewValue()
						.getY()));
			}
		});
	}

	private void fireEvent(Rectangle<T> oldRectangle, Rectangle<T> newRectangle) {
		fireEvent(oldRectangle, newRectangle, false);
	}

	private void fireEvent(Rectangle<T> oldRectangle, Rectangle<T> newRectangle, boolean forceUpdate) {
		if (!ignoreValueChanged) {
			ValueChangedEvent.fireIfNotEqual(this, oldRectangle, newRectangle, forceUpdate);
		}
	}

	public void set(Rectangle<T> rectangle) {
		set(rectangle, false);
	}

	public void set(Rectangle<T> rectangle, boolean forceUpdate) {
		set(rectangle.getX(), rectangle.getY(), rectangle.getWidth(), rectangle.getHeight());
	}

	public void set(Point<T> orgin, Point<T> dimensions) {
		set(orgin, dimensions, false);
	}

	public void set(Point<T> orgin, Point<T> dimensions, boolean forceUpdate) {
		set(orgin.getX(), orgin.getY(), dimensions.getX(), dimensions.getY());
	}

	public void set(T x, T y, T width, T height) {
		set(x, y, width, height, false);
	}

	public void set(T x, T y, T width, T height, boolean forceUpdate) {
		//		ActiveRectangle<T> oldValue = new ActiveRectangle<T>(this.origin.x.get(), this.origin.y.get(),
		//			this.dimensions.x.get(), this.dimensions.y.get());
		ActiveRectangle.Rectangle<T> oldValue = this.get();
		ignoreValueChanged = true;
		origin.set(x, y, forceUpdate);
		dimensions.set(width, height, forceUpdate);
		value = this.get();
		ignoreValueChanged = false;
		fireEvent(oldValue, this.get(), forceUpdate);
	}

	@Override public boolean equals(Object other) {
		//		return other != null && this.getClass().isAssignableFrom(other.getClass())
		//			&& equals((ActiveRectangle<T>) other);
		return other != null && other instanceof ActiveRectangle<?> && equals((ActiveRectangle<T>) other);
	}

	public boolean equals(ActiveRectangle<T> other) {
		return other != null && origin.equals(other.origin) && dimensions.equals(other.dimensions);
	}

	/**
	 * Creates and returns a static representation of the current values.
	 * 
	 * @return static rectangle representation
	 */
	public Rectangle<T> get() {
		return createRectangle(origin.x.get(), origin.y.get(), dimensions.x.get(), dimensions.y.get());
	}

	public static <X> Rectangle<X> createRectangle(final X x, final X y, final X width, final X height) {
		return new Rectangle<X>() {

			@Override public X getX() {
				return x;
			}

			@Override public X getY() {
				return y;
			}

			@Override public X getWidth() {
				return width;
			}

			@Override public X getHeight() {
				return height;
			}
		};
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override public String toString() {
		return super.toString("origin=" + origin + "; dimensions=" + dimensions);
	}

}
